package org.loon.framework.android.game.core.graphics.opengl.particle;

import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;

import org.loon.framework.android.game.action.sprite.ISprite;
import org.loon.framework.android.game.core.LObject;
import org.loon.framework.android.game.core.LSystem;
import org.loon.framework.android.game.core.geom.RectBox;
import org.loon.framework.android.game.core.geom.Vector2f;
import org.loon.framework.android.game.core.graphics.LColor;
import org.loon.framework.android.game.core.graphics.opengl.GL;
import org.loon.framework.android.game.core.graphics.opengl.GL10;
import org.loon.framework.android.game.core.graphics.opengl.GLEx;
import org.loon.framework.android.game.core.graphics.opengl.LTexture;
import org.loon.framework.android.game.core.graphics.opengl.LTexture.Format;
import org.loon.framework.android.game.core.timer.LTimer;
import org.loon.framework.android.game.utils.MathUtils;
import org.loon.framework.android.game.utils.TextureUtils;

/**
 * Copyright 2008 - 2011
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
/**
 *  
 * @project loonframework
 * @author chenpeng
 * @email：ceponline@yahoo.com.cn
 * @version 0.1
 */
public abstract class ParticleSystem extends LObject implements ISprite {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private boolean usePoints, useSmooth;

	public LTexture texture;

	protected LColor color;

	private int howManyEffects;

	protected int blendMode = GL.MODE_NORMAL;

	protected int minNumParticles;

	protected int maxNumParticles = 1;

	protected String textureFilename;

	protected float minInitialSpeed;

	protected float maxInitialSpeed;

	protected float minAcceleration;

	protected float maxAcceleration;

	protected float minRotationSpeed;

	protected float maxRotationSpeed;

	protected float minLifetime;

	protected float maxLifetime;

	protected float minScale;

	protected float maxScale;

	Format format = Format.DEFAULT;

	Particle[] particles;

	ArrayBlockingQueue<Particle> freeParticles;

	boolean isVisible, isClose;

	LColor maskColor1, maskColor2;

	protected ParticleSystem(String fileName) {
		this(1, fileName, null, null);
	}

	protected ParticleSystem(String fileName, LColor mask) {
		this(1, fileName, mask, null);
	}

	protected ParticleSystem(int howManyEffects, String fileName) {
		this(howManyEffects, fileName, null, null);
	}

	protected ParticleSystem(int howManyEffects, String fileName,
			LColor mask1, LColor mask2) {
		this.howManyEffects = howManyEffects;
		this.textureFilename = fileName;
		this.maskColor1 = mask1;
		this.maskColor2 = mask2;
		this.useSmooth = true;
		this.usePoints = false;
		this.isVisible = true;
	}

	public void initialize() {
		initializeConstants();
		particles = new Particle[howManyEffects * maxNumParticles];
		freeParticles = new ArrayBlockingQueue<Particle>(howManyEffects
				* maxNumParticles);
		for (int i = 0; i < particles.length; i++) {
			particles[i] = new Particle();
			freeParticles.offer(particles[i]);
		}
	}

	public void setMaskColor(LColor c) {
		this.maskColor1 = c;
	}

	public void setMaskColorLimit(LColor c1, LColor c2) {
		this.maskColor1 = c1;
		this.maskColor2 = c2;
	}

	protected abstract void initializeConstants();

	protected void loadContent() {
		if (texture != null) {
			texture.destroy();
			texture = null;
		}
		if (maskColor1 == null && maskColor2 == null) {
			texture = new LTexture(textureFilename, format);
		} else if (maskColor1 != null && maskColor2 == null) {
			texture = TextureUtils.filterColor(textureFilename, maskColor1,
					format);
		} else if (maskColor1 != null && maskColor2 != null) {
			texture = TextureUtils.filterLimitColor(textureFilename,
					maskColor1, maskColor2, format);
		}
	}

	public void addParticles(Vector2f where) {
		if (particles == null) {
			initialize();
		}
		Vector2f position = where.cpy();
		int numParticles = LSystem.random.nextInt(maxNumParticles)
				+ minNumParticles;
		int freeCount = freeParticles.size();
		for (int i = 0; i < numParticles && freeCount > 0; i++, freeCount--) {
			Particle p = null;
			p = freeParticles.poll();
			initializeParticle(p, position);
		}
	}

	protected float[] pickRandomDirection() {
		float angle = getRandomBetween(0, MathUtils.TWO_PI);
		return new float[] { MathUtils.cos(angle), MathUtils.sin(angle) };
	}

	public static float getRandomBetween(float min, float max) {
		return min + LSystem.random.nextFloat() * (max - min);
	}

	protected void initializeParticle(Particle p, Vector2f where) {
		float[] direction = pickRandomDirection();
		float velocity = getRandomBetween(minInitialSpeed, maxInitialSpeed);
		float acceleration = getRandomBetween(minAcceleration, maxAcceleration);
		float lifetime = getRandomBetween(minLifetime, maxLifetime);
		float scale = getRandomBetween(minScale, maxScale);
		float rotationSpeed = getRandomBetween(minRotationSpeed,
				maxRotationSpeed);
		Vector2f vel = new Vector2f(direction).scale(velocity);
		Vector2f accel = new Vector2f(direction).scale(acceleration);
		p.initialize(where, vel, accel, lifetime, scale, rotationSpeed);
	}

	private LTimer timer = new LTimer(10);

	private boolean isDirty;

	public void setDelay(long d) {
		timer.setDelay(d);
	}

	public long getDelay() {
		return timer.getDelay();
	}

	public void update(long elapsedTime) {
		if (!isVisible || isClose) {
			return;
		}
		if (particles == null) {
			initialize();
		}
		if (texture == null) {
			loadContent();
		}
		if (timer.action(elapsedTime)) {
			if (!isDirty) {
				synchronized (particles) {
					for (Particle p : particles) {
						if (p.isActive()) {
							p.update(elapsedTime);
							if (!p.isActive()) {
								freeParticles.offer(p);
							}
						}
					}
					isDirty = true;
				}
			}
		}
	}

	public void createUI(GLEx g) {
		createUI(g, 0, 0);
	}

	public void createUI(GLEx g, float x, float y) {
		if (!isVisible || isClose) {
			return;
		}

		if (texture != null) {

			if (x != 0 || y != 0) {
				g.translate(x, y);
			}

			if (useSmooth) {
				g.glShadeModel(GL10.GL_SMOOTH);
			}
			if (usePoints) {
				g.glEnable(GL.GL_POINT_SMOOTH);
			}

			int old = g.getBlendMode();
			g.setBlendMode(blendMode);

			if (alpha > 0.1 && alpha < 1) {
				g.setAlpha(alpha);
			}

			synchronized (particles) {
				if (isDirty) {
					texture.glBegin();
					for (Particle p : particles) {
						if (!p.isActive()) {
							continue;
						}
						if (p.position == null) {
							return;
						}
						float normalizedLifetime = p.timeSinceStart
								/ p.lifetime;
						float alpha = 4 * normalizedLifetime
								* (1 - normalizedLifetime);
						float scale = p.scale
								* (0.75f + 0.25f * normalizedLifetime);
						if (alpha != 1) {
							if (color == null) {
								color = new LColor(1f, 1f, 1f, alpha);
							} else {
								color.a = alpha;
							}
						}
						texture.draw(p.position.x, p.position.y, scale
								* texture.getWidth(),
								scale * texture.getHeight(), 0, 0,
								texture.getWidth(), texture.getHeight(),
								MathUtils.toDegrees(p.rotation), color);
					}
					texture.glEnd();
					texture.newBatchCache();
					isDirty = false;
				} else {
					texture.postLastBatchCache();
				}
			}
			g.setBlendMode(old);

			if (usePoints) {
				g.glDisable(GL.GL_POINT_SMOOTH);
			}

			if (useSmooth) {
				g.glShadeModel(GL10.GL_FLAT);
			}

			if (alpha > 0.1 && alpha < 1) {
				g.setAlpha(1.0F);
			}

			if (x != 0 || y != 0) {
				g.translate(-x, -y);
			}

		}

	}

	public boolean useSmooth() {
		return useSmooth;
	}

	public void setUseSmooth(boolean use) {
		this.useSmooth = use;
	}

	public boolean usePoints() {
		return usePoints;
	}

	public void setUsePoints(boolean use) {
		this.usePoints = use;
	}

	public int getBlendMode() {
		return blendMode;
	}

	public void setBlendMode(int b) {
		this.blendMode = b;
	}

	public int freeParticleCount() {
		return freeParticles.size();
	}

	public final Random getRandom() {
		return LSystem.random;
	}

	public int getHeight() {
		return 0;
	}

	public int getWidth() {
		return 0;
	}

	public LTexture getBitmap() {
		return null;
	}

	public RectBox getCollisionBox() {
		return null;
	}

	public Format getFormat() {
		return format;
	}

	public void setFormat(Format format) {
		this.format = format;
	}

	public boolean isVisible() {
		return isVisible;
	}

	public void setVisible(boolean isVisible) {
		this.isVisible = isVisible;
	}

	public void dispose() {
		if (texture != null) {
			texture.destroy();
			texture = null;
		}
		isClose = true;
	}
}
